<!--
 @description 选择编辑域
 @fileName index.vue
 @author zengqiongying
 @created 2023-04-20 10:08:38
-->
<template>
  <div class="c-field-select">
    <Cell
      :title="hideTitle ? '' : title"
      :subTitle="subTitle"
      :modelValue="curName"
      :placeholder="placeholder"
      :required="required"
      :right="!disabled"
      readonly
      is-input
      :right-icon="iconDirection === 'down' ? 'arrow-down' : ''"
      :input-align="inputAlign"
      :border="border"
      :cell-class-name="cellClassName"
      v-bind="$attrs"
      @right="onClickOpen"
      @clear="onClickClear"
    />
    <PopSelect
      v-if="showPop"
      v-model="curSel"
      :title="title"
      v-model:show="showPop"
      v-model:label="curName"
      :value-name="valueName"
      :label-name="labelName"
      :label-func="labelFunc"
      :list="optionList"
      :clear="showClear"
      :multiple="multiple"
      :height="selHeight"
      @selectChange="onChange"
    />
  </div>
</template>

<script lang="ts">
  import { computed, reactive, toRefs, watch } from 'vue'
  import Cell from '../Cell/index.vue'
  import PopSelect from '../PopSelect/index.vue'
  import request from '@/plugins/request'
  import { isDef } from '@/utils'
  const none: any[] = [null, undefined, '']

  interface datasModle {
    showPop: boolean
    curName: string
    list: any[]
    curSel: string | any[]
    templabel: any
    exCodeList: any[]
    inCodeList: any[]
  }
  interface paramsRawModel {
    [key: string]: any
  }
  interface requestModel {
    url: string
    method: string
    data?: any
    headers?: any
  }
  export default {
    name: 'FieldSelect',
    components: { Cell, PopSelect },
    inheritAttrs: true,
    props: {
      title: {
        type: String,
        default: ''
      }, // 标题
      subTitle: {
        type: String,
        default: undefined
      },
      hideTitle: {
        type: Boolean,
        default: false
      },
      label: {
        type: [String, Array],
        default: undefined
      }, // 选中的值的名称
      modelValue: {
        type: [String, Number, Array],
        default: ''
      }, // 选中的值
      required: {
        type: Boolean,
        default: false
      },
      placeholder: {
        type: String,
        default: '请选择'
      },
      disabled: {
        type: Boolean,
        default: false
      },
      optionData: {
        // 选项列表数据，该数据不为空时，不请求远程搜索
        type: Array,
        default: null
      },
      valueName: {
        // 选项值键名
        type: String,
        default: 'id'
      },
      labelName: {
        // 选项名称键名
        type: String,
        default: ''
      },
      labelFunc: {
        // 选项名称自定义回调方法
        type: Function,
        default: null
      },
      currentId: {
        // 选中值的Id
        type: String,
        default: ''
      },
      curOptions: {
        // 当前选中选项，默认为对象，multiple为true时为数组
        type: [Array, Object],
        default: () => {
          return {}
        }
      },
      customName: {
        // 输入值的查询键名，有值时使用，否则使用labelName
        type: String,
        default: ''
      },
      customOperator: {
        // 输入值的查询键名匹配符，有值时使用，否则使用labelName
        type: String,
        default: 'like' // like 、 =
      },
      url: {
        // 查询地址
        type: String,
        default: ''
      },
      method: {
        // 请求方式 get/post,默认post
        type: String,
        default: 'post'
      },
      params: {
        // 请求参数,query为具体参数，type为传参方式（1 body方式,0 formData标准分页,2 formData方式）
        type: Object,
        default: () => {
          return { query: null, type: 0 }
        }
      },
      paramsRaw: {
        // 标准分页查询额外参数(对应分页查询入参：paramsRaw)
        type: Object,
        default: () => ({})
      },
      parentId: {
        // 父级参数(父级Id或code)，改变时刷新列表
        type: [String, Number, Array],
        default: ''
      },
      mustPid: {
        // 是否必须有父级id
        type: Boolean,
        default: false
      },
      mustVal: {
        // 是否必须有值查询
        type: Boolean,
        default: false
      },
      reqHeaders: {
        // 请求头参数
        type: Object,
        default: null
      },
      includes: {
        // 包含指定的字典编码，若为Function则返回值需要是字典编码数组
        type: [Array, Function],
        default: undefined
      },
      excludes: {
        // 排除指定的字典编码，若为Function则返回值需要是字典编码数组
        type: [Array, Function],
        default: undefined
      },
      multiple: {
        // 是否多选
        type: Boolean,
        default: false
      },
      clear: {
        // 是否显示清除按钮
        type: Boolean,
        default: false
      },
      inputAlign: {
        // 输入框对齐方式：left/ right
        type: String,
        default: 'right'
      },
      border: {
        // 是否有边框
        type: Boolean,
        default: true
      },
      cellClassName: {
        type: String,
        default: ''
      },
      iconDirection: {
        // 右侧箭头方向：right/down
        type: String,
        default: 'right'
      }
    },
    setup(props: any, ctx: any) {
      const datas = reactive<datasModle>({
        showPop: false,
        curName: '',
        list: [],
        curSel: '',
        templabel: null, // 选中项的name值，没有绑定label时使用
        exCodeList: [],
        inCodeList: []
      })
      const optionList = computed(() => {
        let t = datas.list
        if (datas.inCodeList instanceof Array && datas.inCodeList.length > 0) {
          t = t.filter((e: any) =>
            datas.inCodeList.some(
              (v: any) => isDef(e[props.valueName]) && v.toString() === e[props.valueName].toString()
            )
          )
        }
        if (datas.exCodeList instanceof Array && datas.exCodeList.length > 0) {
          t = t.filter(
            (e: any) =>
              !datas.exCodeList.some(
                (v: any) => isDef(e[props.valueName]) && v.toString() === e[props.valueName].toString()
              )
          )
        }
        return t
      })
      const selHeight = computed(() => {
        let h = ''
        if (optionList.value.length > 6) {
          h = '40vh'
        }
        return h
      })
      const showClear = computed(() => {
        if (!props.clear) return false
        if (props.multiple) {
          return props.modelValue instanceof Array && props.modelValue.length > 0
        }
        return !none.includes(props.modelValue)
      })
      const onClickOpen = () => {
        if (props.disabled) return
        datas.showPop = true
      }
      const onChange = (data: any) => {
        let val: any = props.multiple ? [] : ''
        if (data) {
          val = props.multiple
            ? data.map((v: any) => {
                return v[props.valueName]
              })
            : data[props.valueName]
        }

        if (props.multiple) {
          const nameList: any[] = []
          const ary: any[] = []
          const ids: any[] = []
          if (val && val.length > 0) {
            val.forEach((code: any) => {
              const item = optionList.value.find((e) => {
                return e[props.valueName].toString() === code.toString()
              })
              nameList.push(item[props.labelName])
              ary.push(item)
              if (item.id) {
                ids.push(item.id)
              }
            })
          }
          ctx.emit('update:label', nameList)
          ctx.emit('update:curOptions', ary)
          ctx.emit('update:currentId', ids)
          ctx.emit('change', ary)
        } else {
          const obj = !none.includes(val)
            ? optionList.value.find((e) => {
                return e[props.valueName].toString() === val.toString()
              })
            : ''
          const name = obj ? obj[props.labelName] : ''
          datas.templabel = name
          ctx.emit('update:label', name)
          ctx.emit('update:curOptions', obj || {})
          ctx.emit('update:currentId', obj ? obj.id : '')
          ctx.emit('change', obj)
        }
        ctx.emit('update:modelValue', val)
      }

      const onClickClear = () => {
        onChange(null)
      }

      const setList = (val: any) => {
        // 判断列表中是否有传入的选项
        if (props.multiple) {
          for (const i in val) {
            const bol = !none.includes(val)
              ? datas.list.every((e) => {
                  return isDef(e[props.valueName]) && e[props.valueName].toString() !== val[i].toString()
                })
              : false
            // 没有当前选项则加上
            if (bol) {
              if (props.label) {
                const labelIndex = Number(i)
                const d = {
                  [props.labelName]: props.label[labelIndex] || '',
                  [props.valueName]: val[i]
                }
                datas.list.unshift(d)
              } else {
                const d = {
                  [props.labelName]: datas.templabel ? datas.templabel[i] || '' : '',
                  [props.valueName]: val[i]
                }
                datas.list.unshift(d)
              }
            }
          }

          if (!props.label) {
            const nameList: any[] = []
            if (val && val instanceof Array) {
              datas.list.forEach((e) => {
                if (isDef(e[props.valueName]) && val.includes(e[props.valueName].toString())) {
                  nameList.push(e[props.labelName])
                }
              })
            }
            datas.curName = nameList.join(',')
          } else {
            datas.curName = props.label instanceof Array ? props.label.join(',') : props.label
          }
        } else {
          const bol = !none.includes(val)
            ? datas.list.every((e) => {
                return isDef(e[props.valueName]) && e[props.valueName].toString() !== val.toString()
              })
            : false

          // 没有当前选项则加上
          if (bol && props.modelValue) {
            const d = {
              [props.labelName]: props.label ? props.label : datas.templabel || '',
              [props.valueName]: val
            }
            datas.list.unshift(d)
          }
          if (!props.label) {
            const i = datas.list.findIndex(
              (e) => isDef(e[props.valueName]) && e[props.valueName].toString() === val.toString()
            )
            datas.curName = i > -1 ? datas.list[i][props.labelName] : ''
          } else {
            datas.curName = props.label instanceof Array ? props.label.join(',') : props.label
          }
        }
      }
      const getCludeList = () => {
        let inCodeList = []
        if (props.includes) {
          if (props.includes instanceof Array) {
            inCodeList = props.includes
          } else if (props.includes instanceof Function) {
            try {
              const result = props.includes()
              if (result instanceof Promise) {
                result.then((data) => {
                  inCodeList = data instanceof Array ? data : []
                })
              } else {
                inCodeList = result instanceof Array ? result : []
              }
            } catch (e) {
              datas.inCodeList = []
            }
          }
        }
        datas.inCodeList = inCodeList

        let exCodeList = []
        if (props.excludes) {
          if (props.excludes instanceof Array) {
            exCodeList = props.excludes || []
          } else if (props.excludes instanceof Function) {
            try {
              const result = props.excludes()
              if (result instanceof Promise) {
                result.then((data) => {
                  exCodeList = data instanceof Array ? data : []
                })
              } else {
                exCodeList = result instanceof Array ? result : []
              }
            } catch (e) {
              exCodeList = []
            }
          }
        }
        datas.exCodeList = exCodeList
      }

      const remoteMethod = (val: any) => {
        if (props.optionData) {
          datas.list = props.optionData
          setList(datas.curSel)
          return
        }
        if (props.mustPid && !props.parentId) {
          datas.list = []
          setList(datas.curSel)
          return
        }
        if (props.mustVal && !val) {
          return
        }
        const formObj = new FormData()
        const filtersRaw = []
        const obj = { ...props.params.query }
        if (val) delete obj[props.customName]
        if (obj) {
          for (const i in obj) {
            if (Array.isArray(obj[i]) && obj[i].length === 2) {
              filtersRaw.push({
                id: Math.floor(Math.random() * 0xffffffffff).toString(16),
                value: obj[i][0],
                property: i,
                operator: '>='
              })
              filtersRaw.push({
                id: Math.floor(Math.random() * 0xffffffffff).toString(16),
                value: obj[i][1],
                property: i,
                operator: '<='
              })
            } else if (obj.hasOwnProperty(i) && Object.prototype.toString.call(obj[i]) === '[object Object]') {
              const v = obj[i]
              if (v.value instanceof Array) {
                if (v.value.length <= 0) {
                  continue
                } else {
                  v.value = `[${v.value.join(',')}]`
                }
              }
              filtersRaw.push({
                id: Math.floor(Math.random() * 0xffffffffff).toString(16),
                value: v.value,
                property: i,
                operator: v.operator
              })
            } else if (obj[i] !== '[]' && !none.includes(obj[i])) {
              filtersRaw.push({
                id: Math.floor(Math.random() * 0xffffffffff).toString(16),
                value: obj[i],
                property: i,
                operator: 'like'
              })
            }
          }
        }
        if (val) {
          filtersRaw.push({
            id: Math.floor(Math.random() * 0xffffffffff).toString(16),
            value: val,
            property: props.customName ? props.customName : props.labelName,
            operator: props.customName ? props.customOperator : 'like'
          })
        }
        const paramsRaw: paramsRawModel = {}
        for (const [k, v] of Object.entries(props.paramsRaw)) {
          if (none.includes(v)) {
            continue
          }
          if (v instanceof Array) {
            const _v = v.filter((e) => !none.includes(e))
            if (_v.length > 0) {
              // paramsRaw[k] = `[${_v.join(',')}]`
              paramsRaw[k] = _v
            }
          } else if (Object.prototype.toString.call(v) === '[object Object]') {
            paramsRaw[k] = JSON.stringify(v)
          }
        }
        formObj.append('page', '1')
        formObj.append('rows', '50')
        formObj.append('filtersRaw', JSON.stringify(filtersRaw))
        formObj.append('paramsRaw', JSON.stringify(paramsRaw))
        // const obj = this.params.query
        const strList = ['page=1', 'rows=50']
        let str = ''
        for (const i in obj) {
          if (!none.includes(obj[i])) {
            strList.push(i + '=' + obj[i])
          }
        }
        if (val) {
          strList.push((props.customName ? props.customName : props.labelName) + '=' + val)
        }
        if (strList.length) {
          str = '?' + strList.join('&')
        }

        let reqConfig: requestModel = {
          url: '',
          method: ''
        }
        if (props.method.toLowerCase() === 'get') {
          reqConfig = {
            url: props.url + str,
            method: props.method
          }
        } else {
          let reqData: any = ''
          if (props.params.type === 0) {
            // 标准filter查询
            reqData = formObj
          } else if (props.params.type === 3) {
            // JSON数据
            reqData = { ...obj, ...{ page: 1, rows: 50 } }
          } else if (props.params.type === 2) {
            // 普通formData
            reqData = new FormData()
            for (const key in obj) {
              reqData.append(key, obj[key])
            }
            reqData.append('page', 1)
            reqData.append('rows', 50)
          }
          reqConfig = {
            url: props.url + (props.params.type === 1 ? str : ''),
            method: props.method,
            data: reqData
          }
        }
        if (props.reqHeaders && Object.keys(props.reqHeaders).length > 0) {
          reqConfig.headers = props.reqHeaders
        }

        getCludeList()

        request(reqConfig)
          .then((res: any) => {
            if (res.success === 1) {
              if (Object.keys(res.data).indexOf('records') === -1) {
                datas.list = res.data.map((e: any) => {
                  e[props.valueName] = e[props.valueName].toString()
                  return e
                })
              } else {
                datas.list = res.data.records.map((e: any) => {
                  e[props.valueName] = e[props.valueName].toString()
                  return e
                })
              }
              ctx.emit('data-change', datas.list)
              setList(datas.curSel)
            }
          })
          .catch(() => {})
      }

      watch(
        () => props.modelValue,
        (val) => {
          if (props.multiple) {
            const tmpCurSel: any[] = []
            if (val && val instanceof Array) {
              val.forEach((e: any) => {
                if (!none.includes(e)) {
                  tmpCurSel.push(e.toString())
                }
              })
            }
            datas.curSel = tmpCurSel
          } else {
            datas.curSel = none.includes(val) ? '' : val.toString()
            if (!val) {
              datas.templabel = null
            }
          }
          // 数据请求未返回时，设置默认选项
          setList(datas.curSel)
        },
        {
          deep: true,
          immediate: true
        }
      )
      watch(
        () => props.url,
        () => {
          remoteMethod(null)
        },
        { immediate: true }
      )
      // 监听parentId实现父级变化后刷新列表
      watch(
        () => props.parentId,
        () => {
          remoteMethod(null)
        },
        {}
      )
      watch(
        () => props.optionData,
        () => {
          remoteMethod(null)
        },
        { immediate: true }
      )
      watch(
        () => props.includes,
        () => {
          getCludeList()
        },
        { deep: true }
      )
      watch(
        () => props.excludes,
        () => {
          getCludeList()
        },
        { deep: true }
      )

      getCludeList()

      return {
        ...toRefs(datas),
        optionList,
        selHeight,
        showClear,
        onClickOpen,
        onClickClear,
        onChange
      }
    }
  }
</script>
<style lang="less" scoped></style>
